/*
 * ________________________________________________________________________________________________________
 * Copyright (c) 2018-2019 InvenSense Inc. All rights reserved.
 *
 * This software, related documentation and any modifications thereto (collectively "Software") is subject
 * to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
 * and other intellectual property rights laws.
 *
 * InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
 * and any use, reproduction, disclosure or distribution of the Software without an express license agreement
 * from InvenSense is strictly prohibited.
 *
 * EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
 * PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
 * INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THE SOFTWARE.
 * ________________________________________________________________________________________________________
 */

#include "ICM-4x6xx.h"
#include "icm406xx_driver.h"
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>


#define DRIVER_VERSION "0.0.01"
#define I2C_BURST_READ_MAX_LEN      (256)
#define icm406xx_STORE_COUNT  (6000)
#define LMADA     (1)
#define CONFIG_USE_QUALCOMM_HAL
uint64_t g_current_apts_us;


enum ICM_SENSOR_INT_T {
	/* Interrupt enable0*/
	ICM_ANYMO_X_INT = 0,
	ICM_ANYMO_Y_INT,
	ICM_ANYMO_Z_INT,
	ICM_D_TAP_INT,
	ICM_S_TAP_INT,
	ICM_ORIENT_INT,
	ICM_FLAT_INT,
	/* Interrupt enable1*/
	ICM_HIGH_X_INT,
	ICM_HIGH_Y_INT,
	ICM_HIGH_Z_INT,
	ICM_LOW_INT,
	ICM_DRDY_INT,
	ICM_FFULL_INT,
	ICM_FWM_INT,
	/* Interrupt enable2 */
	ICM_NOMOTION_X_INT,
	ICM_NOMOTION_Y_INT,
	ICM_NOMOTION_Z_INT,
	ICM_STEP_DETECTOR_INT,
	INT_TYPE_MAX
};

/*icm fifo sensor type combination*/
enum ICM_SENSOR_FIFO_COMBINATION {
	ICM_FIFO_A = 0,
	ICM_FIFO_G,
	ICM_FIFO_M,
	ICM_FIFO_G_A,
	ICM_FIFO_M_A,
	ICM_FIFO_M_G,
	ICM_FIFO_M_G_A,
	ICM_FIFO_COM_MAX
};

/*icm fifo analyse return err status*/
enum ICM_FIFO_ANALYSE_RETURN_T {
	FIFO_OVER_READ_RETURN = -10,
	FIFO_SENSORTIME_RETURN = -9,
	FIFO_SKIP_OVER_LEN = -8,
	FIFO_M_G_A_OVER_LEN = -7,
	FIFO_M_G_OVER_LEN = -6,
	FIFO_M_A_OVER_LEN = -5,
	FIFO_G_A_OVER_LEN = -4,
	FIFO_M_OVER_LEN = -3,
	FIFO_G_OVER_LEN = -2,
	FIFO_A_OVER_LEN = -1
};

/*!icm sensor generic power mode enum */
enum ICM_DEV_OP_MODE {
	SENSOR_PM_NORMAL = 0,
	SENSOR_PM_LP1,
	SENSOR_PM_SUSPEND,
	SENSOR_PM_LP2
};

/*! icm acc sensor power mode enum */
enum ICM_ACC_PM_TYPE {
	ICM_ACC_PM_NORMAL = 0,
	ICM_ACC_PM_LP1,
	ICM_ACC_PM_SUSPEND,
	ICM_ACC_PM_LP2,
	ICM_ACC_PM_MAX
};

/*! icm gyro sensor power mode enum */
enum ICM_GYRO_PM_TYPE {
	ICM_GYRO_PM_NORMAL = 0,
	ICM_GYRO_PM_FAST_START,
	ICM_GYRO_PM_SUSPEND,
	ICM_GYRO_PM_MAX
};

/*! icm mag sensor power mode enum */
enum ICM_MAG_PM_TYPE {
	ICM_MAG_PM_NORMAL = 0,
	ICM_MAG_PM_LP1,
	ICM_MAG_PM_SUSPEND,
	ICM_MAG_PM_LP2,
	ICM_MAG_PM_MAX
};


/*! icm sensor support type*/
enum ICM_SENSOR_TYPE {
	ICM_ACC_SENSOR,
	ICM_GYRO_SENSOR,
	ICM_MAG_SENSOR,
	ICM_SENSOR_TYPE_MAX
};

/*!icm sensor generic power mode enum */
enum ICM_AXIS_TYPE {
	X_AXIS = 0,
	Y_AXIS,
	Z_AXIS,
	AXIS_MAX
};

/*!icm sensor generic intterrupt enum */
enum ICM_INT_TYPE {
	icm406xx_INT0 = 0,
	icm406xx_INT1,
	icm406xx_INT_MAX
};

/*! icm sensor time resolution definition*/
enum ICM_SENSOR_TIME_RS_TYPE {
	TS_0_78_HZ = 1,/*0.78HZ*/
	TS_1_56_HZ,/*1.56HZ*/
	TS_3_125_HZ,/*3.125HZ*/
	TS_6_25_HZ,/*6.25HZ*/
	TS_12_5_HZ,/*12.5HZ*/
	TS_25_HZ,/*25HZ, odr=6*/
	TS_50_HZ,/*50HZ*/
	TS_100_HZ,/*100HZ*/
	TS_200_HZ,/*200HZ*/
	TS_400_HZ,/*400HZ*/
	TS_800_HZ,/*800HZ*/
	TS_1600_HZ,/*1600HZ*/
	TS_MAX_HZ
};

/*! icm sensor interface mode */
enum ICM_SENSOR_IF_MODE_TYPE {
	/*primary interface:autoconfig/secondary interface off*/
	P_AUTO_S_OFF = 0,
	/*primary interface:I2C/secondary interface:OIS*/
	P_I2C_S_OIS,
	/*primary interface:autoconfig/secondary interface:Magnetometer*/
	P_AUTO_S_MAG,
	/*interface mode reseved*/
	IF_MODE_RESEVED

};

/*! icm406xx acc/gyro calibration status in H/W layer */
enum ICM_CALIBRATION_STATUS_TYPE {
	/*ICM FAST Calibration ready x/y/z status*/
	ICM_ACC_X_FAST_CALI_RDY = 0,
	ICM_ACC_Y_FAST_CALI_RDY,
	ICM_ACC_Z_FAST_CALI_RDY
};

unsigned int reg_op_addr;

static const int icm_pmu_cmd_acc_arr[ICM_ACC_PM_MAX] = {
	/*!icm pmu for acc normal, low power1,
	 * suspend, low power2 mode command */
	CMD_PMU_ACC_NORMAL,
	CMD_PMU_ACC_LP1,
	CMD_PMU_ACC_SUSPEND,
	CMD_PMU_ACC_LP2
};

static const int icm_pmu_cmd_gyro_arr[ICM_GYRO_PM_MAX] = {
	/*!icm pmu for gyro normal, fast startup,
	 * suspend mode command */
	CMD_PMU_GYRO_NORMAL,
	CMD_PMU_GYRO_FASTSTART,
	CMD_PMU_GYRO_SUSPEND
};

static const int icm_pmu_cmd_mag_arr[ICM_MAG_PM_MAX] = {
	/*!icm pmu for mag normal, low power1,
	 * suspend, low power2 mode command */
	CMD_PMU_MAG_NORMAL,
	CMD_PMU_MAG_LP1,
	CMD_PMU_MAG_SUSPEND,
	CMD_PMU_MAG_LP2
};

static const char *icm_axis_name[AXIS_MAX] = {"x", "y", "z"};

static const int icm_interrupt_type[] = {
	/*!icm interrupt type */
	/* Interrupt enable0 , index=0~6*/
	icm406xx_ANY_MOTION_X_ENABLE,
	icm406xx_ANY_MOTION_Y_ENABLE,
	icm406xx_ANY_MOTION_Z_ENABLE,
	icm406xx_DOUBLE_TAP_ENABLE,
	icm406xx_SINGLE_TAP_ENABLE,
	icm406xx_ORIENT_ENABLE,
	icm406xx_FLAT_ENABLE,
	/* Interrupt enable1, index=7~13*/
	icm406xx_HIGH_G_X_ENABLE,
	icm406xx_HIGH_G_Y_ENABLE,
	icm406xx_HIGH_G_Z_ENABLE,
	icm406xx_LOW_G_ENABLE,
	icm406xx_DATA_RDY_ENABLE,
	icm406xx_FIFO_FULL_ENABLE,
	icm406xx_FIFO_WM_ENABLE,
	/* Interrupt enable2, index = 14~17*/
	icm406xx_NOMOTION_X_ENABLE,
	icm406xx_NOMOTION_Y_ENABLE,
	icm406xx_NOMOTION_Z_ENABLE,
	icm406xx_STEP_DETECTOR_EN
};

/*! icm sensor time depend on ODR*/
struct icm_sensor_time_odr_tbl {
	u32 ts_duration_lsb;
	u32 ts_duration_us;
	u32 ts_delat;/*sub current delat fifo_time*/
};

struct icm406xx_axis_data_t {
	s16 x;
	s16 y;
	s16 z;
};

struct icm406xx_type_mapping_type {

	/*! sensor chip id */
	uint16_t chip_id;

	/*! chip revision code */
	uint16_t revision_id;

	/*! sensor name */
	const char *sensor_name;
};

struct icm406xx_store_info_t {
	uint8_t current_frm_cnt;
	uint64_t current_apts_us[2];
	uint8_t fifo_ts_total_frmcnt;
	uint64_t fifo_time;
};

uint64_t get_current_timestamp(void)
{
	uint64_t ts;
	struct timeval tv;

	do_gettimeofday(&tv);
	ts = (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;

	return ts;
}


/*! sensor support type map */
static const struct icm406xx_type_mapping_type sensor_type_map[] = {

	{SENSOR_CHIP_ID_ICM, SENSOR_CHIP_REV_ID_ICM, "icm40607"},

};

/*!icm406xx sensor time depends on ODR */
static const struct icm_sensor_time_odr_tbl
		sensortime_duration_tbl[TS_MAX_HZ] = {
	{0x010000, 2560000, 0x00ffff},/*2560ms, 0.39hz, odr=resver*/
	{0x008000, 1280000, 0x007fff},/*1280ms, 0.78hz, odr_acc=1*/
	{0x004000, 640000, 0x003fff},/*640ms, 1.56hz, odr_acc=2*/
	{0x002000, 320000, 0x001fff},/*320ms, 3.125hz, odr_acc=3*/
	{0x001000, 160000, 0x000fff},/*160ms, 6.25hz, odr_acc=4*/
	{0x000800, 80000,  0x0007ff},/*80ms, 12.5hz*/
	{0x000400, 40000, 0x0003ff},/*40ms, 25hz, odr_acc = odr_gyro =6*/
	{0x000200, 20000, 0x0001ff},/*20ms, 50hz, odr = 7*/
	{0x000100, 10000, 0x0000ff},/*10ms, 100hz, odr=8*/
	{0x000080, 5000, 0x00007f},/*5ms, 200hz, odr=9*/
	{0x000040, 2500, 0x00003f},/*2.5ms, 400hz, odr=10*/
	{0x000020, 1250, 0x00001f},/*1.25ms, 800hz, odr=11*/
	{0x000010, 625, 0x00000f},/*0.625ms, 1600hz, odr=12*/

};

#if defined(CONFIG_USE_QUALCOMM_HAL)
#define POLL_INTERVAL_MIN_MS	10
#define POLL_INTERVAL_MAX_MS	4000
#define POLL_DEFAULT_INTERVAL_MS 200
#define ICM406XX_ACCEL_MIN_VALUE	-32768
#define ICM406XX_ACCEL_MAX_VALUE	32767
#define ICM406XX_GYRO_MIN_VALUE	-32768
#define ICM406XX_GYRO_MAX_VALUE	32767
#define icm406xx_ACCEL_DEFAULT_POLL_INTERVAL_MS	200
#define icm406xx_GYRO_DEFAULT_POLL_INTERVAL_MS	200
#define icm406xx_ACCEL_MIN_POLL_INTERVAL_MS	10
#define icm406xx_ACCEL_MAX_POLL_INTERVAL_MS	5000
#define icm406xx_GYRO_MIN_POLL_INTERVAL_MS	10
#define icm406xx_GYRO_MAX_POLL_INTERVAL_MS	5000
static struct sensors_classdev icm406xx_accel_cdev = {
		.name = "icm406xx-accel",
		.vendor = "InvenSense",
		.version = 1,
		.handle = SENSORS_ACCELERATION_HANDLE,
		.type = SENSOR_TYPE_ACCELEROMETER,
		.max_range = "156.8",	/* 16g */
		.resolution = "0.153125",	/* 15.6mg */
		.sensor_power = "0.13",	/* typical value */
		.min_delay = POLL_INTERVAL_MIN_MS * 1000, /* in microseconds */
		.max_delay = POLL_INTERVAL_MAX_MS,
		.delay_msec = POLL_DEFAULT_INTERVAL_MS, /* in millisecond */
		.fifo_reserved_event_count = 0,
		.fifo_max_event_count = 0,
		.enabled = 0,
		.max_latency = 0,
		.flags = 0,
		.sensors_enable = NULL,
		.sensors_poll_delay = NULL,
		.sensors_set_latency = NULL,
		.sensors_flush = NULL,
		.sensors_self_test = NULL,
};
static struct sensors_classdev icm406xx_gyro_cdev = {
	.name = "icm406xx-gyro",
	.vendor = "InvenSense",
	.version = 1,
	.handle = SENSORS_GYROSCOPE_HANDLE,
	.type = SENSOR_TYPE_GYROSCOPE,
	.max_range = "34.906586",	/* rad/s */
	.resolution = "0.0010681152",	/* rad/s */
	.sensor_power = "3.6",	/* 3.6 mA */
	.min_delay = icm406xx_GYRO_MIN_POLL_INTERVAL_MS * 1000,
	.max_delay = icm406xx_GYRO_MAX_POLL_INTERVAL_MS,
	.delay_msec = icm406xx_GYRO_DEFAULT_POLL_INTERVAL_MS,
	.fifo_reserved_event_count = 0,
	.fifo_max_event_count = 0,
	.enabled = 0,
	.max_latency = 0,
	.flags = 0, /* SENSOR_FLAG_CONTINUOUS_MODE */
	.sensors_enable = NULL,
	.sensors_poll_delay = NULL,
	.sensors_enable_wakeup = NULL,
	.sensors_set_latency = NULL,
	.sensors_flush = NULL,
};
#endif
static void icm_delay(u32 msec)
{
	if (msec <= 20)
		usleep_range(msec * 1000, msec * 1000);
	else
		msleep(msec);
}

static void icm_dump_reg(struct icm_client_data *client_data)
{
	#define REG_MAX0 0x24
	#define REG_MAX1 0x56
	int i;
	u8 dbg_buf0[REG_MAX0];
	u8 dbg_buf1[REG_MAX1];
	u8 dbg_buf_str0[REG_MAX0 * 3 + 1] = "";
	u8 dbg_buf_str1[REG_MAX1 * 3 + 1] = "";

	dev_notice(client_data->dev, "\nFrom 0x00:\n");

	client_data->device.bus_read(client_data->device.dev_addr,
			ICM_REG_NAME(USER_CHIP_ID), dbg_buf0, REG_MAX0);
	for (i = 0; i < REG_MAX0; i++) {
		snprintf(dbg_buf_str0 + i * 3, 16, "%02x%c", dbg_buf0[i],
				(((i + 1) % BYTES_PER_LINE == 0) ? '\n' : ' '));
	}
	dev_notice(client_data->dev, "%s\n", dbg_buf_str0);

	client_data->device.bus_read(client_data->device.dev_addr,
			icm406xx_USER_ACCEL_CONFIG_ADDR, dbg_buf1, REG_MAX1);
	dev_notice(client_data->dev, "\nFrom 0x40:\n");
	for (i = 0; i < REG_MAX1; i++) {
		snprintf(dbg_buf_str1 + i * 3, 16, "%02x%c", dbg_buf1[i],
				(((i + 1) % BYTES_PER_LINE == 0) ? '\n' : ' '));
	}
	dev_notice(client_data->dev, "\n%s\n", dbg_buf_str1);
}

static int icm_input_init(struct icm_client_data *client_data)
{
	struct input_dev *dev;
	int err = 0;

	dev = input_allocate_device();
	if (NULL == dev)
		return -ENOMEM;

	dev->name = SENSOR_NAME;
	dev->id.bustype = BUS_I2C;

	input_set_capability(dev, EV_MSC, INPUT_EVENT_SGM);
	input_set_capability(dev, EV_MSC, INPUT_EVENT_STEP_DETECTOR);
	input_set_capability(dev, EV_MSC, INPUT_EVENT_FAST_ACC_CALIB_DONE);
	input_set_capability(dev, EV_MSC, INPUT_EVENT_FAST_GYRO_CALIB_DONE);

	input_set_capability(dev, EV_REL, REL_X);
	input_set_capability(dev, EV_REL, REL_Y);
	input_set_capability(dev, EV_REL, REL_Z);
	
#if defined(CONFIG_USE_QUALCOMM_HAL)
	input_set_capability(dev, EV_ABS, ABS_MISC);
	input_set_abs_params(dev, ABS_X,
		ICM406XX_ACCEL_MIN_VALUE, ICM406XX_ACCEL_MAX_VALUE, 0, 0);
	input_set_abs_params(dev, ABS_Y,
		ICM406XX_ACCEL_MIN_VALUE, ICM406XX_ACCEL_MAX_VALUE, 0, 0);
	input_set_abs_params(dev, ABS_Z,
		ICM406XX_ACCEL_MIN_VALUE, ICM406XX_ACCEL_MAX_VALUE, 0, 0);
#endif
	input_set_drvdata(dev, client_data);

	err = input_register_device(dev);
	if (err < 0) {
		input_free_device(dev);
		dev_notice(client_data->dev, "icm406xx input free!\n");
		return err;
	}
	client_data->input = dev;
	dev_notice(client_data->dev,
		"icm406xx input register successfully, %s!\n",
		client_data->input->name);
	return err;
}

#if defined(CONFIG_USE_QUALCOMM_HAL)
static int icm_gyro_input_init(struct icm_client_data *client_data)
{
	struct input_dev *dev;
	int err = 0;

	dev = input_allocate_device();
	if (NULL == dev)
		return -ENOMEM;
	dev->name = "icm406xx_gyro";
	dev->id.bustype = BUS_I2C;
	input_set_capability(dev, EV_ABS, ABS_MISC);
	input_set_abs_params(dev, ABS_RX,
	ICM406XX_GYRO_MIN_VALUE, ICM406XX_GYRO_MAX_VALUE, 0, 0);
	input_set_abs_params(dev, ABS_RY,
	ICM406XX_GYRO_MIN_VALUE, ICM406XX_GYRO_MAX_VALUE, 0, 0);
	input_set_abs_params(dev, ABS_RZ,
	ICM406XX_GYRO_MIN_VALUE, ICM406XX_GYRO_MAX_VALUE, 0, 0);
	input_set_drvdata(dev, client_data);
	err = input_register_device(dev);
	if (err < 0) {
		input_free_device(dev);
		dev_notice(client_data->dev, "icm406xx input free!\n");
		return err;
	}
	client_data->gyro_input = dev;
	dev_notice(client_data->dev,
		"icm406xx input register successfully, %s!\n",
		client_data->gyro_input->name);
	return err;
}
#endif
static void icm_input_destroy(struct icm_client_data *client_data)
{
	struct input_dev *dev = client_data->input;

	input_unregister_device(dev);
	input_free_device(dev);
}

static int icm_check_chip_id(struct icm_client_data *client_data)
{
	int8_t err = 0;
	int8_t i = 0;
	uint8_t chip_id = 0;
	uint8_t read_count = 0;
	u8 icm_sensor_cnt = sizeof(sensor_type_map)
				/ sizeof(struct icm406xx_type_mapping_type);
	/* read and check chip id */
	while (read_count++ < CHECK_CHIP_ID_TIME_MAX) {
		if (client_data->device.bus_read(client_data->device.dev_addr,
				0x75, &chip_id, 1) < 0) {

			dev_err(client_data->dev,
					"InvenSense Device not found"
						"read chip_id:%d\n", chip_id);
			continue;
		} else {
			for (i = 0; i < icm_sensor_cnt; i++) {
				if (sensor_type_map[i].chip_id == chip_id) {
					client_data->chip_id = chip_id;
					dev_notice(client_data->dev,
					"InvenSense Device detected, "
			"HW IC name: %s\n", sensor_type_map[i].sensor_name);
					break;
				}
			}
			if (i < icm_sensor_cnt)
				break;
			else {
				if (read_count == CHECK_CHIP_ID_TIME_MAX) {
					dev_err(client_data->dev,
				"Failed! InvenSense Device not found"
					" mismatch chip_id:%d\n", chip_id);
					err = -ENODEV;
					return err;
				}
			}
			icm_delay(1);
		}
	}
	return err;

}

static int icm_pmu_set_suspend(struct icm_client_data *client_data)
{
	int err = 0;
	if (client_data == NULL)
		return -EINVAL;
	else {
		ICM4x6xx_Accel_off();
		ICM4x6xx_Gyro_off();
		client_data->pw.acc_pm = ICM_ACC_PM_SUSPEND;
		client_data->pw.gyro_pm = ICM_GYRO_PM_SUSPEND;
	}

	return err;
}

static ssize_t icm406xx_chip_id_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct input_dev *input = to_input_dev(dev);
	struct icm_client_data *client_data = input_get_drvdata(input);

	return snprintf(buf, 16, "0x%x\n", client_data->chip_id);
}

static int icm406xx_set_acc_op_mode(struct icm_client_data *client_data,
							unsigned long op_mode)
{
	int err = 0;
	unsigned char stc_enable;
	unsigned char std_enable;
	mutex_lock(&client_data->mutex_op_mode);

	if (op_mode < ICM_ACC_PM_MAX) {
		switch (op_mode) {
		case ICM_ACC_PM_NORMAL:
			ICM4x6xx_Accel_on();
			client_data->pw.acc_pm = ICM_ACC_PM_NORMAL;
			icm_delay(10);
			break;
		case ICM_ACC_PM_SUSPEND:
			ICM4x6xx_Accel_off();
			client_data->pw.acc_pm = ICM_ACC_PM_SUSPEND;
			icm_delay(10);
			break;
		default:
			mutex_unlock(&client_data->mutex_op_mode);
			return -EINVAL;
		}
	} else {
		mutex_unlock(&client_data->mutex_op_mode);
		return -EINVAL;
	}

	mutex_unlock(&client_data->mutex_op_mode);

	return err;


}

static ssize_t icm406xx_place_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct input_dev *input = to_input_dev(dev);
	struct icm_client_data *client_data = input_get_drvdata(input);
	int place = INVN_SENSOR_PLACE_UNKNOWN;

	if (NULL != client_data->bst_pd)
		place = client_data->bst_pd->place;

	return snprintf(buf, 16, "%d\n", place);
}

static ssize_t icm406xx_delay_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct input_dev *input = to_input_dev(dev);
	struct icm_client_data *client_data = input_get_drvdata(input);

	return snprintf(buf, 16, "%d\n", atomic_read(&client_data->delay));

}

static ssize_t icm406xx_delay_store(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct input_dev *input = to_input_dev(dev);
	struct icm_client_data *client_data = input_get_drvdata(input);
	int err;
	unsigned long data;

	err = kstrtoul(buf, 10, &data);
	if (err)
		return err;

	if (data == 0) {
		err = -EINVAL;
		return err;
	}

	if (data < ICM_DELAY_MIN)
		data = ICM_DELAY_MIN;

	atomic_set(&client_data->delay, (unsigned int)data);

	return count;
}

static ssize_t icm406xx_acc_value_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	short data[3];

	int err;

	ICM4x6xx_GetAccelData_UI(data);

	return snprintf(buf, 48, "%hd %hd %hd\n",
			data[0], data[1], data[2]);
}

/* gyro sensor part */
static ssize_t icm406xx_gyro_value_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int data[3];
	int err;

	ICM4x6xx_GetGyroData_UI(&data);

	return snprintf(buf, 48, "%hd %hd %hd\n", data[0], data[1], data[2]);
}

static ssize_t icm406xx_driver_version_show(struct device *dev
		, struct device_attribute *attr, char *buf)
{
	struct input_dev *input = to_input_dev(dev);
	struct icm_client_data *client_data = input_get_drvdata(input);
	int ret;

	if (client_data == NULL) {
		printk(KERN_ERR "Invalid client_data pointer");
		return -ENODEV;
	}

	ret = snprintf(buf, 128, "Driver version: %s\n",
			DRIVER_VERSION);

	return ret;
}

static DEVICE_ATTR(chip_id, S_IRUGO,
		icm406xx_chip_id_show, NULL);
static DEVICE_ATTR(place, S_IRUGO,
		icm406xx_place_show, NULL);
static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH,
		icm406xx_delay_show, icm406xx_delay_store);
static DEVICE_ATTR(acc_value, S_IRUGO,
		icm406xx_acc_value_show, NULL);
static DEVICE_ATTR(driver_version, S_IRUGO,
		icm406xx_driver_version_show, NULL);
/* gyro part */
static DEVICE_ATTR(gyro_value, S_IRUGO,
		icm406xx_gyro_value_show, NULL);


static struct attribute *icm406xx_attributes[] = {
	&dev_attr_chip_id.attr,

	&dev_attr_driver_version.attr,

	&dev_attr_enable.attr,
	&dev_attr_delay.attr,

	&dev_attr_place.attr,

	&dev_attr_acc_value.attr,

	&dev_attr_icm_value.attr,
	NULL
};

static struct attribute_group icm406xx_attribute_group = {
	.attrs = icm406xx_attributes
};

#if defined(CONFIG_USE_QUALCOMM_HAL)
static void icm406xx_accel_work_fn(struct work_struct *work)
{
	struct icm_client_data *sensor;
	ktime_t timestamp;

	short data[3];
	int err;
	sensor = container_of((struct delayed_work *)work,
				struct icm_client_data, accel_poll_work);
	timestamp = ktime_get();
	ICM4x6xx_GetAccelData_UI(data);
	if (err)
		dev_err(sensor->dev, "read data err");
	input_report_abs(sensor->input, ABS_X,
		(data[0]));
	input_report_abs(sensor->input, ABS_Y,
		(data[1]));
	input_report_abs(sensor->input, ABS_Z,
		(data[2]));
	input_event(sensor->input,
			EV_SYN, SYN_TIME_SEC,
			ktime_to_timespec(timestamp).tv_sec);
	input_event(sensor->input, EV_SYN,
		SYN_TIME_NSEC,
		ktime_to_timespec(timestamp).tv_nsec);
	input_sync(sensor->input);
	if (atomic_read(&sensor->accel_en))
		queue_delayed_work(sensor->data_wq,
			&sensor->accel_poll_work,
			msecs_to_jiffies(sensor->accel_poll_ms));
}
static void icm406xx_gyro_work_fn(struct work_struct *work)
{
	struct icm_client_data *sensor;
	ktime_t timestamp;
	short data[3];
	int err;
	sensor = container_of((struct delayed_work *)work,
				struct icm_client_data, gyro_poll_work);
	timestamp = ktime_get();
	ICM4x6xx_GetGyroData_UI(data);
	if (err)
		dev_err(sensor->dev, "read data err");
	input_report_abs(sensor->gyro_input, ABS_RX,
		(data[0]));
	input_report_abs(sensor->gyro_input, ABS_RY,
		(data[1]));
	input_report_abs(sensor->gyro_input, ABS_RZ,
		(data[2]));
	input_event(sensor->gyro_input,
			EV_SYN, SYN_TIME_SEC,
			ktime_to_timespec(timestamp).tv_sec);
	input_event(sensor->gyro_input, EV_SYN,
		SYN_TIME_NSEC,
		ktime_to_timespec(timestamp).tv_nsec);
	input_sync(sensor->gyro_input);
	if (atomic_read(&sensor->gyro_en))
		queue_delayed_work(sensor->data_wq,
			&sensor->gyro_poll_work,
			msecs_to_jiffies(sensor->gyro_poll_ms));
}
static int icm406xx_set_gyro_op_mode(struct icm_client_data *client_data,
							unsigned long op_mode)
{
	int err = 0;
	mutex_lock(&client_data->mutex_op_mode);
	if (op_mode < ICM_GYRO_PM_MAX) {
		switch (op_mode) {
		case ICM_GYRO_PM_NORMAL:
			ICM4x6xx_Gyro_on();
			client_data->pw.gyro_pm = ICM_GYRO_PM_NORMAL;
			icm_delay(60);
			break;
		case ICM_GYRO_PM_SUSPEND:
			ICM4x6xx_Gyro_off();
			client_data->pw.gyro_pm = ICM_ACC_PM_SUSPEND;
			icm_delay(60);
			break;
		default:
			mutex_unlock(&client_data->mutex_op_mode);
			return -EINVAL;
		}
	} else {
		mutex_unlock(&client_data->mutex_op_mode);
		return -EINVAL;
	}
	mutex_unlock(&client_data->mutex_op_mode);
	return err;
}
static int icm406xx_accel_set_enable(
	struct icm_client_data *client_data, bool enable)
{
	int ret = 0;
	dev_notice(client_data->dev,
		"icm406xx_accel_set_enable enable=%d\n", enable);
	if (enable) {
		ICM4x6xx_Accel_on();

		queue_delayed_work(client_data->data_wq,
				&client_data->accel_poll_work,
				msecs_to_jiffies(client_data->accel_poll_ms));
		atomic_set(&client_data->accel_en, 1);
		client_data->pw.acc_pm = ICM_ACC_PM_NORMAL;
	} else {
		atomic_set(&client_data->accel_en, 0);
		cancel_delayed_work_sync(&client_data->accel_poll_work);
		ICM4x6xx_Accel_off();
		client_data->pw.acc_pm = ICM_ACC_PM_SUSPEND;
	}
exit:
	return ret;
}
static int icm406xx_accel_set_poll_delay(struct icm_client_data *client_data,
					unsigned long delay)
{
	dev_info(client_data->dev,
		"icm406xx_accel_set_poll_delay delay_ms=%ld\n", delay);
	if (delay < icm406xx_ACCEL_MIN_POLL_INTERVAL_MS)
		delay = icm406xx_ACCEL_MIN_POLL_INTERVAL_MS;
	if (delay > icm406xx_ACCEL_MAX_POLL_INTERVAL_MS)
		delay = icm406xx_ACCEL_MAX_POLL_INTERVAL_MS;
	client_data->accel_poll_ms = delay;
	if (!atomic_read(&client_data->accel_en))
		goto exit;
	cancel_delayed_work_sync(&client_data->accel_poll_work);
	queue_delayed_work(client_data->data_wq,
			&client_data->accel_poll_work,
			msecs_to_jiffies(client_data->accel_poll_ms));
exit:
	return 0;
}
static int icm406xx_gyro_set_enable(
	struct icm_client_data *client_data, bool enable)
{
	int ret = 0;
	dev_notice(client_data->dev,
		"icm406xx_gyro_set_enable enable=%d\n", enable);
	if (enable) {
		ICM4x6xx_Gyro_on();
		queue_delayed_work(client_data->data_wq,
				&client_data->gyro_poll_work,
				msecs_to_jiffies(client_data->gyro_poll_ms));
		atomic_set(&client_data->gyro_en, 1);
		client_data->pw.gyro_pm = ICM_GYRO_PM_NORMAL;
	} else {
		atomic_set(&client_data->gyro_en, 0);
		cancel_delayed_work_sync(&client_data->gyro_poll_work);
		ICM4x6xx_Gyro_off();
		client_data->pw.gyro_pm = ICM_GYRO_PM_SUSPEND;
	}
exit:
	return ret;
}
static int icm406xx_gyro_set_poll_delay(struct icm_client_data *client_data,
					unsigned long delay)
{
	dev_info(client_data->dev,
		"icm406xx_accel_set_poll_delay delay_ms=%ld\n", delay);
	if (delay < icm406xx_GYRO_MIN_POLL_INTERVAL_MS)
		delay = icm406xx_GYRO_MIN_POLL_INTERVAL_MS;
	if (delay > icm406xx_GYRO_MAX_POLL_INTERVAL_MS)
		delay = icm406xx_GYRO_MAX_POLL_INTERVAL_MS;
	client_data->gyro_poll_ms = delay;
	if (!atomic_read(&client_data->gyro_en))
		goto exit;
	cancel_delayed_work_sync(&client_data->gyro_poll_work);
	queue_delayed_work(client_data->data_wq,
			&client_data->gyro_poll_work,
			msecs_to_jiffies(client_data->gyro_poll_ms));
exit:
	return 0;
}
static int icm406xx_accel_cdev_enable(struct sensors_classdev *sensors_cdev,
			unsigned int enable)
{
	struct icm_client_data *sensor = container_of(sensors_cdev,
			struct icm_client_data, accel_cdev);
	return icm406xx_accel_set_enable(sensor, enable);
}
static int icm406xx_accel_cdev_poll_delay(struct sensors_classdev *sensors_cdev,
			unsigned int delay_ms)
{
	struct icm_client_data *sensor = container_of(sensors_cdev,
			struct icm_client_data, accel_cdev);

	return icm406xx_accel_set_poll_delay(sensor, delay_ms);
}

static int icm406xx_gyro_cdev_enable(struct sensors_classdev *sensors_cdev,
			unsigned int enable)
{
	struct icm_client_data *sensor = container_of(sensors_cdev,
			struct icm_client_data, gyro_cdev);

	return icm406xx_gyro_set_enable(sensor, enable);
}

static int icm406xx_gyro_cdev_poll_delay(struct sensors_classdev *sensors_cdev,
			unsigned int delay_ms)
{
	struct icm_client_data *sensor = container_of(sensors_cdev,
			struct icm_client_data, gyro_cdev);

	return	icm406xx_gyro_set_poll_delay(sensor, delay_ms);
}
#endif

int icm_probe(struct icm_client_data *client_data, struct device *dev)
{
	int err = 0;
#ifdef icm406xx_MAG_INTERFACE_SUPPORT
	u8 mag_dev_addr;
	u8 mag_urst_len;
	u8 mag_op_mode;
#endif
	/* check chip id */
	err = icm_check_chip_id(client_data);
	if (err)
		goto exit_err_clean;

	dev_set_drvdata(dev, client_data);
	client_data->dev = dev;

	mutex_init(&client_data->mutex_enable);
	mutex_init(&client_data->mutex_op_mode);

	/* input device init */
	err = icm_input_init(client_data);
	if (err < 0)
		goto exit_err_clean;

	/* sysfs node creation */
	err = sysfs_create_group(&client_data->input->dev.kobj,
			&icm406xx_attribute_group);

	if (err < 0)
		goto exit_err_sysfs;

	if (NULL != dev->platform_data) {
		client_data->bst_pd = kzalloc(sizeof(*client_data->bst_pd),
				GFP_KERNEL);

		if (NULL != client_data->bst_pd) {
			memcpy(client_data->bst_pd, dev->platform_data,
					sizeof(*client_data->bst_pd));
			dev_notice(dev, "%s sensor driver set place: p%d\n",
					client_data->bst_pd->name,
					client_data->bst_pd->place);
		}
	}

	if (NULL != client_data->bst_pd) {
			memcpy(client_data->bst_pd, dev->platform_data,
					sizeof(*client_data->bst_pd));
			dev_notice(dev, "%s sensor driver set place: p%d\n",
					client_data->bst_pd->name,
					client_data->bst_pd->place);
		}

	/* h/w init */
	client_data->device.delay_msec = icm_delay;
	err = inv_serif_init(&client_data->device);

	/*power on detected*/
	/*or softrest */
	/*fatal err check*/
	/*soft reset*/
	ICM4x6xx_init();

	icm_delay(3);

	client_data->selftest = 0;

	client_data->fifo_data_sel = 0;
	#if defined(CONFIG_USE_QUALCOMM_HAL)
	ICM4x6xx_accel_odr(7);/*idx=7, odr 200HZ*/
	ICM4x6xx_gyro_odr(7);/*idx=7, odr 200HZ*/
	#endif

	/* now it's power on which is considered as resuming from suspend */
#if defined(CONFIG_USE_QUALCOMM_HAL)
	/* gyro input device init */
	err = icm_gyro_input_init(client_data);
	if (err < 0)
		goto exit_err_clean;
	client_data->accel_poll_ms = icm406xx_ACCEL_DEFAULT_POLL_INTERVAL_MS;
	client_data->gyro_poll_ms = icm406xx_GYRO_DEFAULT_POLL_INTERVAL_MS;
	client_data->data_wq = create_freezable_workqueue("icm406xx_data_work");
	if (!client_data->data_wq) {
		dev_err(dev, "Cannot create workqueue!\n");
		goto exit_err_clean;
	}
	INIT_DELAYED_WORK(&client_data->accel_poll_work, icm406xx_accel_work_fn);
	client_data->accel_cdev = icm406xx_accel_cdev;
	client_data->accel_cdev.delay_msec = client_data->accel_poll_ms;
	client_data->accel_cdev.sensors_enable = icm406xx_accel_cdev_enable;
	client_data->accel_cdev.sensors_poll_delay = icm406xx_accel_cdev_poll_delay;
	err = sensors_classdev_register(dev, &client_data->accel_cdev);
	if (err) {
		dev_err(dev,
			"create accel class device file failed!\n");
		goto exit_err_clean;
	}
	INIT_DELAYED_WORK(&client_data->gyro_poll_work, icm406xx_gyro_work_fn);
	client_data->gyro_cdev = icm406xx_gyro_cdev;
	client_data->gyro_cdev.delay_msec = client_data->gyro_poll_ms;
	client_data->gyro_cdev.sensors_enable = icm406xx_gyro_cdev_enable;
	client_data->gyro_cdev.sensors_poll_delay = icm406xx_gyro_cdev_poll_delay;
	err = sensors_classdev_register(dev, &client_data->gyro_cdev);
	if (err) {
		dev_err(dev,
			"create accel class device file failed!\n");
		goto exit_err_clean;
	}
#endif
	/* set sensor PMU into suspend power mode for all */
	if (icm_pmu_set_suspend(client_data) < 0) {
		dev_err(dev, "Failed to set icm406xx to suspend power mode\n");
		goto exit_err_sysfs;
	}

	dev_notice(dev, "sensor_time:%d, %d, %d",
		sensortime_duration_tbl[0].ts_delat,
		sensortime_duration_tbl[0].ts_duration_lsb,
		sensortime_duration_tbl[0].ts_duration_us);
	dev_notice(dev, "sensor %s probed successfully", SENSOR_NAME);

	return 0;

exit_err_sysfs:
	if (err)
		icm_input_destroy(client_data);

exit_err_clean:
	if (err) {
		if (client_data != NULL) {
			if (NULL != client_data->bst_pd) {
				kfree(client_data->bst_pd);
				client_data->bst_pd = NULL;
			}
		}
	}
	return err;
}
EXPORT_SYMBOL(icm_probe);

/*!
 * @brief remove icm client
 *
 * @param dev the pointer of device
 *
 * @return zero
 * @retval zero
*/
int icm_remove(struct device *dev)
{
	int err = 0;
	struct icm_client_data *client_data = dev_get_drvdata(dev);

	if (NULL != client_data) {
#ifdef CONFIG_HAS_EARLYSUSPEND
		unregister_early_suspend(&client_data->early_suspend_handler);
#endif
		mutex_lock(&client_data->mutex_enable);
		if (ICM_ACC_PM_NORMAL == client_data->pw.acc_pm ||
			ICM_GYRO_PM_NORMAL == client_data->pw.gyro_pm ||
				ICM_MAG_PM_NORMAL == client_data->pw.mag_pm) {
			cancel_delayed_work_sync(&client_data->work);
		}
		mutex_unlock(&client_data->mutex_enable);

		err = icm_pmu_set_suspend(client_data);

		icm_delay(5);

		sysfs_remove_group(&client_data->input->dev.kobj,
				&icm406xx_attribute_group);
		icm_input_destroy(client_data);

		if (NULL != client_data->bst_pd) {
			kfree(client_data->bst_pd);
			client_data->bst_pd = NULL;
		}
		kfree(client_data);
	}

	return err;
}
EXPORT_SYMBOL(icm_remove);

static int icm_post_resume(struct icm_client_data *client_data)
{
	int err = 0;

	mutex_lock(&client_data->mutex_enable);

	if (atomic_read(&client_data->accel_en) == 1) {
		icm406xx_set_acc_op_mode(client_data, ICM_ACC_PM_NORMAL);
		schedule_delayed_work(&client_data->accel_poll_work,
				msecs_to_jiffies(
					atomic_read(&client_data->delay)));
	}

	if (atomic_read(&client_data->gyro_en) == 1) {
		icm406xx_set_gyro_op_mode(client_data, ICM_GYRO_PM_NORMAL);
		schedule_delayed_work(&client_data->gyro_poll_work,
				msecs_to_jiffies(
					atomic_read(&client_data->delay)));
	}
	mutex_unlock(&client_data->mutex_enable);

	return err;
}


int icm_suspend(struct device *dev)
{
	int err = 0;
	struct icm_client_data *client_data = dev_get_drvdata(dev);

	dev_err(client_data->dev, "icm suspend function entrance");


	if (atomic_read(&client_data->accel_en) == 1) {
		icm406xx_set_acc_op_mode(client_data, ICM_ACC_PM_SUSPEND);
		cancel_delayed_work_sync(&client_data->work);
	}
	if (atomic_read(&client_data->gyro_en) == 1) {
		icm406xx_set_gyro_op_mode(client_data, ICM_GYRO_PM_SUSPEND);
		cancel_delayed_work_sync(&client_data->work);
	}

	return err;
}
EXPORT_SYMBOL(icm_suspend);

int icm_resume(struct device *dev)
{
	int err = 0;
	struct icm_client_data *client_data = dev_get_drvdata(dev);
	
	if (client_data->pw.acc_pm != ICM_ACC_PM_SUSPEND) {
		icm406xx_set_acc_op_mode(client_data, ICM_ACC_PM_NORMAL);
		icm_delay(3);
	}
	if (client_data->pw.gyro_pm != ICM_GYRO_PM_SUSPEND) {
		icm406xx_set_acc_op_mode(client_data, ICM_GYRO_PM_NORMAL);
		icm_delay(3);
	}

	/* post resume operation */
	err += icm_post_resume(client_data);

	return err;
}
EXPORT_SYMBOL(icm_resume);

